#include <assert.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include "thread_list.h"
#include "user_mutex.h"
#include "user_threads.h"
#include "common.h"

struct umutex_struct {
        uthread_id owner_uthread_id; //ID of holding thread. -1 if unlocked
        thread_list *wait_queue; //lock queue
        unsigned int lock_count;
};

thread_list *mutex_list;

int is_mutex_locked(uthread_mutex_t m) {
	int retVal =  m->owner_uthread_id >= 0;
	if (retVal < 0) {
		assert(m->lock_count == 0);
	}
	return retVal;
}

int is_mutex_initialized(uthread_mutex_t m) {
	return thread_list_is_in(mutex_list,m);
}

int is_mutex_queue_empty(uthread_mutex_t m) {
	return thread_list_is_empty(m->wait_queue);
}

int uthread_mutex_init_general() {
	mutex_list = thread_list_create();
	if (!mutex_list) {
		return MUTEX_FAILURE;
	}
	return MUTEX_SUCCESS;
}

int uthread_mutex_init(uthread_mutex_t* mutex) {
	lock_alarm();
	if(!mutex){
		unlock_alarm();
		return MUTEX_FAILURE;
	}
	/* no need to check if mutex already initialized */
	*mutex = (uthread_mutex_t)malloc(sizeof(struct umutex_struct));
	if (!(*mutex)){
		unlock_alarm();
		return MUTEX_FAILURE;
	}
	(*mutex)->owner_uthread_id = -1;
	(*mutex)->wait_queue = thread_list_create();
	if (!((*mutex)->wait_queue)) {
		free (*mutex);
		unlock_alarm();
		return MUTEX_FAILURE;
	}
	(*mutex)->lock_count = 0;
	if (thread_list_insert_head(mutex_list, (void*)*mutex) != HW3_SUCCESS){
		thread_list_destroy((*mutex)->wait_queue);
		free(*mutex);
		unlock_alarm();
		return MUTEX_FAILURE;
	}
	unlock_alarm();
	return MUTEX_SUCCESS;
}

int uthread_mutex_destroy(uthread_mutex_t mutex) {
	lock_alarm();
	if (!mutex){
		unlock_alarm();
		return MUTEX_INVALID;
	}
	node* curr = thread_list_find(mutex_list, mutex);
	if (curr == NULL){
		/* our 'test' of initialized mutex is if he is in the global list mutex_list */
		unlock_alarm();
		return MUTEX_UNINITIALIZED;
	}
	if (is_mutex_locked(mutex)) {
		unlock_alarm();
		return MUTEX_LOCKED;
	}

	assert(thread_list_is_empty(mutex->wait_queue));
	if (thread_list_destroy(mutex->wait_queue) != HW3_SUCCESS) {
		assert(0);
	}
	if (!thread_list_remove(mutex_list, mutex)) {
		assert(0);
	}
	free(mutex);
	unlock_alarm();
	return MUTEX_SUCCESS;
}

int uthread_mutex_lock(uthread_mutex_t mutex) {
	lock_alarm();
	if (!mutex) {
		unlock_alarm();
		return MUTEX_INVALID;
	}
	if (!is_mutex_initialized(mutex)){
		unlock_alarm();
		return MUTEX_UNINITIALIZED;
	}
	if (!is_mutex_locked(mutex)) {
		assert(mutex->lock_count == 0);
		assert(thread_list_is_empty(mutex->wait_queue)); //IDAN BORIS: might be not empty.
		mutex->owner_uthread_id = uthread_self();
        if(thread_list_insert_head(current->locked_mutexes, mutex) != HW3_SUCCESS){
                //inserts this mutex into the list of mutexes we are currently holding
                unlock_alarm();
                return MUTEX_FAILURE;
        }
        mutex->lock_count = 1; //=1
		unlock_alarm();
		return MUTEX_SUCCESS;
	}
	if (mutex->owner_uthread_id == current->id){
		/* implementing the recursive functionality */
        mutex->lock_count++;
		unlock_alarm();
		return MUTEX_SUCCESS;
	}

	if (thread_list_insert_tail(mutex->wait_queue, current) != HW3_SUCCESS) {
		/* this inserts at the end of the list */
		unlock_alarm();
		return MUTEX_FAILURE;
	}
	current->mutex_waiting_on = mutex; //remembering that we're waiting for this mutex
	deactivate_thread_and_cs(current);
	assert(current->mutex_waiting_on == NULL);
	assert(mutex->owner_uthread_id == current->id);
	unlock_alarm();
	return MUTEX_SUCCESS; //this line will run when the mutex is finally ours
}

int uthread_mutex_try_lock(uthread_mutex_t mutex) {
	lock_alarm();
	if (!mutex) {
		unlock_alarm();
		return MUTEX_INVALID;
	}
	if (!is_mutex_initialized(mutex)){
		unlock_alarm();
		return MUTEX_UNINITIALIZED;
	}
	if (mutex->owner_uthread_id == uthread_self()){
		assert(is_mutex_locked(mutex));
		assert(mutex->lock_count > 0);
		mutex->lock_count++;
		unlock_alarm();
		return MUTEX_SUCCESS;
	}
	if (is_mutex_locked(mutex)) {
		unlock_alarm();
		return MUTEX_LOCKED;
	}
	assert(thread_list_is_empty(mutex->wait_queue));
	mutex->owner_uthread_id = uthread_self();
    if(thread_list_insert_head(current->locked_mutexes, mutex) != HW3_SUCCESS){
            //inserts this mutex into the list of mutexes we are currently holding
            unlock_alarm();
            return MUTEX_FAILURE;
    }
    assert(mutex->lock_count == 0);
    mutex->lock_count = 1;
	unlock_alarm();
	return MUTEX_SUCCESS;
}

int uthread_mutex_unlock(uthread_mutex_t mutex) {
	if (!mutex)
		return MUTEX_INVALID;
	lock_alarm();
	if (!is_mutex_initialized(mutex)){
		unlock_alarm();
		return MUTEX_UNINITIALIZED;
	}
	if (!is_mutex_locked(mutex)){
		unlock_alarm();
		return MUTEX_UNLOCKED; //TODO: get mail from TA - is the return value correct?
	}
	if (mutex->owner_uthread_id != uthread_self()){ //check if we are the owners of this mutex
		unlock_alarm();
		return MUTEX_LOCKED;
	}
	thread_list* locked_mutexes = current->locked_mutexes;
    node* n = thread_list_find(locked_mutexes,mutex);
    if (!n) {
		assert(0);
    }
    mutex->lock_count--;
    if (mutex->lock_count == 0) {
		if(!thread_list_remove(locked_mutexes, mutex)){ //remove this mutex from the list of mutexes we hold
			assert(0);
		}
		if (thread_list_is_empty(mutex->wait_queue)){
			mutex->owner_uthread_id = -1;
			unlock_alarm();
			return MUTEX_UNLOCKED;
		}
		mutex->lock_count = 1;
		uthread new_owner;
		new_owner = (uthread) thread_list_remove_head(mutex->wait_queue);
		if (!new_owner){
			assert(0);
		}

		if(thread_list_insert_tail(new_owner->locked_mutexes, mutex) != HW3_SUCCESS){
			//inserts this mutex into the list of mutexes the new thread is currently holding
			unlock_alarm();
			return MUTEX_FAILURE;
		}
		mutex->owner_uthread_id = new_owner->id;
		new_owner->mutex_waiting_on = NULL; //we're not waiting for this mutex anymore
		activate_thread(new_owner);
    }
    else {
    	/* if here then the recursive mutex is still locked */
    }
	unlock_alarm();
	return MUTEX_SUCCESS;
}

void uthread_mutex_free_general()
{
	node *n;
	thread_list_for_each(mutex_list,n) {
		uthread_mutex_t mutex = n->entry;
		thread_list_destroy(mutex->wait_queue);
		free(mutex);
	}
	thread_list_destroy(mutex_list);
}

int uthread_free_mutexes(uthread thread) {
	assert (thread);
	node* n;
	uthread_mutex_t mutex;
	assert(thread->locked_mutexes);
	thread_list_for_each(thread->locked_mutexes, n) {
		mutex = (uthread_mutex_t) n->entry;
		assert(mutex->owner_uthread_id == thread->id);
		if (thread_list_is_empty(mutex->wait_queue)){
			mutex->owner_uthread_id = -1;
			mutex->lock_count = 0;
		}
		else {
			uthread new_owner = thread_list_remove_head(mutex->wait_queue);
			if (!new_owner){
				assert(0);
			}
			if(thread_list_insert_head(new_owner->locked_mutexes, mutex) != HW3_SUCCESS){
				//inserts this mutex into the list of mutexes the
				//new thread is currently holding
				return MUTEX_FAILURE;
			}
			new_owner->mutex_waiting_on = NULL; //we are not waiting for this mutex anymore
			mutex->owner_uthread_id = new_owner->id;
			mutex->lock_count = 1;
			activate_thread(new_owner);
		}
	}

    if (thread->mutex_waiting_on) {
    	assert(!thread_list_is_empty(thread->mutex_waiting_on->wait_queue));
    	if (!thread_list_remove(thread->mutex_waiting_on->wait_queue,thread)) {
    		assert(0);
    	}
    }
    return MUTEX_SUCCESS;
}
